package 函数式编程;

//方法引用的操作符是双冒号"::"。
//
//方法引用可以认为是Lambda表达式的一种特殊形式，Lambda表达式可以让开发者自定义抽象方法的实现代码，方法引用则可以让开发者直接引用已存
//在的实现方法，作为Lambda表达式的Lambda体(参数列表及返回类型得一致)。
//
//先写一个简单的Lambda表达式的例子作为对比：
/*import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
@FunctionalInterface
interface ImTheOne {
    String handleString(String a, String b);
}
 
public class 方法引用1 {
    public static void main(String[] args) {
        ImTheOne theOne = (a, b) -> a + b;//实现接口方法(匿名)：参数：String a和String b,方法返回:String (a+b)
        String result = theOne.handleString("abc", "def");
        System.out.println("方式一："+result);

        //BinaryOprator<T> BiFunction子接口  对类型为T的对象进行二元运算,返回T类型结果，包括方法T apply(T t1，T t2) 
        BinaryOperator<String> operator1=new BinaryOperator<>() {
        	public String apply(String o1,String o2) {
        		return o1+o2;
        	}
        };
        String result1=operator1.apply("abc", "def");
        System.out.println("方式二："+result1);
        
        BinaryOperator<String> operator2=(s1,s2)->s1+s2;
        System.out.println("方式三："+operator2.apply("abc","def"));

    }
}*/
//Lambda体的内容是两个字符串连接，下面列举几个方法引用的实例。
//方法引用的几种形式：

//一，类::静态方法
//如果我们打算把一个类中的静态方法作为Lambda体，可以这样用：

/*@FunctionalInterface
interface ImTheOne {
    String handleString(String a, String b);
}
class OneClass {
    public static String concatString(String o1, String o2) {
        return o1 + o2;
    }
}
 
public class 方法引用1 {
    public static void main(String[] args) {
        ImTheOne theOne = OneClass::concatString;
        String result = theOne.handleString("abc", "def");
        System.out.println(result);
 
        //相当于以下效果，直接把类的静态方法写在Lambda体里
        ImTheOne theOne2 = (a, b) -> OneClass.concatString(a, b);
        String result2 = theOne2.handleString("123", "456");
        System.out.println(result2);
 
    }
}*/
//在这段代码中，把OneClass类的concatString（）方法作为了Lambda表达式的Lambda体。
//效果和Lambda表达式相同，当调用接口的handleString()方法时，调用的实际上是OneClass类的concatString()方法。
//
//注意：
//1，OneClass类的concatString()方法的参数列表，和ImTheOne接口的handleString()方法参数列表(和返回类型)必须一致，才能用方法引用，否则编译报错。
//2，此时的concatString()方法必须标记为静态方法，否则编译会报错。

//二，对象::实例方法
//我们可以把一个实例的非静态方法作为Lambda体，比如这样：

/*@FunctionalInterface
interface ImTheOne {
    String handleString(String a, String b);
}
class OneClass {
    public String concatString(String a, String b) {//非静态方法
        return a + b;
    }
}
class TwoClass {
    public String concatString(String a, String b) {//非静态方法
        return b + a;
    }
}
 
public class 方法引用1 {
    public static void main(String[] args) {
        OneClass oneclass1 = new OneClass();
        ImTheOne theOne = oneclass1::concatString;//对象::实例方法,只有方法的参数和返回类型一致即可
        String result = theOne.handleString("abc", "def");
        System.out.println(result);
 
        //相当于以下效果
        TwoClass twoclass2 = new TwoClass();
        ImTheOne theTwo2 = (a, b) -> twoclass2.concatString(a, b);
        String result2 = theTwo2.handleString("123", "456");
        System.out.println(result2);//456123
 
    }
}*/
//这段代码中，先定义了OneClass的一个对象(oneclass1)，然后把对象的concatString()方法作为Lambda体。
//
//注意：
//1，这种模式下, concatString()方法不能标记为静态方法，否则编译会报错。
//2，这里的对象可以是父对象，比如可以使用：
//super:: concatString
//这种形式（如果有父类有这个方法的话）。

//三，类::实例方法
//这种模式并不是要直接调用类的实例方法，这样显然连编译都过不去。
//这种模式实际上是 对象::实例方法模式的一种变形，当一个对象调用方法时，方法的某个参数是函数式接口，而且函数式接口的方法参数列表的第一个参数
//就是调用者对象所属的类时，可以引用调用者类中定义的，不包含函数式接口第一个参数的方法，并用类::实例方法这种形式来表达，比如这样：

//@FunctionalInterface
//interface ImTheOne<T> {
//    String handleString(T a, String b);
//}
//class OneClass {
//    String oneString;
// 
//    public String concatString(String a) {//实例方法，不含函数式接口第一个参数，也可以被调用。 
//        return this.oneString + a;
//    }
// 
//    public String startHandleString(ImTheOne<OneClass> imtheone, String str) {
//    	return imtheone.handleString(this, str);
//    }
//}
// 
//public class 方法引用1 {
//    public static void main(String[] args) {
//        OneClass oneclass = new OneClass();
//        oneclass.oneString = "abc";//将OneClass类成员变量oneString赋值为"abc"
//        String result = oneclass.startHandleString(OneClass::concatString, "123");
//        System.out.println("方式一："+result);
// 
//        //相当于以下效果
//        OneClass oneclass2 = new OneClass();
//        oneclass2.oneString = "abc";
//        ImTheOne theone2 = (a, b) -> oneclass2.concatString(b);
//        String result2 = theone2.handleString(theone2, "123");
//        System.out.println("方式二："+result2);
//
//        OneClass oneclass3=new OneClass();
//        oneclass3.oneString ="aabbcc";
//        String result3=oneclass3.startHandleString((a,b)->oneclass3.concatString(b), "112233");
//        System.out.println("方式三："+result3);
//    }
//}

//对这种模式的理解大概是这样的：
//当一个对象调用一个方法，方法的参数中包含一个函数式接口，该函数式接口的第一个参数类型是这个对象的类，那么这个函数式接口可用方法引用代替，
//并且替换用的方法可以不包含函数式接口的第一个参数（调用对象的类）。
//看起来比较绕，下面画个表对照代码具体解释一下：
//┌───┬────────────────────────┬───────────────────────────────────────────────────────────────────────────────────┐
//│步骤│    　　 描 　　述         │                               代 码 对 照                                           │
//├───┼────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┤
//│ 1 │当一个对象调用一个方法      　│oneClass对象，调用startHandleString(ImTheOne<OneClass> imTheOne，String str)方法。    　│
//│ 2 │方法的参数中包含一个函数式接口　│ImTheOne<T>这个函数式接口的唯一抽象方法是handleString(T t, String a，String b)，          　│
//│   │        ，             　│第一个参数就是ImTheOne定义时的泛型类，也就是OneClass类,也就是步骤1时调用者oneClass对象所属类。      │
//│ 3 │该函数式接口的第一个参数类型是　│ImTheOne<T>这个函数式接口的唯一抽象方法是handleString(T t, String a，String b)，          　│
//│   │这个对象的类        　    　│第一个参数就是ImTheOne定义时的泛型类，也就是OneClass类,也就是步骤1时调用者oneClass对象属的类。      │
//│ 4 │那么这个函数式接口可用方法   　│对于ImTheOne<OneClass>这个参数,可以用OneClass::concatString这个方法引用来代替               │
//│   │引用代替。               　│                                                                                   │
//│ 5 │并且替换用的方法可以不包含函数　│函数式接口中的handleString方法参数列表是(T t,String a，String b)，3个参数，而方法引中的         │
//│   │式接口的第一个参数         　│concatString方法参数是(String a, String b)，比handleString少了第一个T参数，此时java会为第    │
//│   │        ，          　   │一个参数就是方法的调用者oneClass对象                                                      │
//└───┴────────────────────────┴───────────────────────────────────────────────────────────────────────────────────┘

//另外，这种模式下方法引用的方法必须是调用者对象所属类中的对象，也就是concatString方法必须定义在OneClass类中。
//
//四，构造器引用，Class::new
//这种模式被称为构造方法引用，或构造器引用。
//
//构造方法也是方法，构造方法引用实际上表示一个函数式接口中的唯一方法引用了一个类的构造方法，引用的是那个参数相同的构造方法。
//
//下面举个例子：
//
@FunctionalInterface
interface ImTheOne1 {
    TargetClass getTargetClass(String a);
}
class TargetClass {
    String oneString;
 
    public TargetClass() {
        oneString = "default";
    }
 
    public TargetClass(String a) {
        oneString = a;
    }
}
 
public class 方法引用1 {
    public static void main(String[] args) {
 
        ImTheOne1 imTheOne = TargetClass::new;
        TargetClass targetClass = imTheOne.getTargetClass("abc");
        System.out.println(targetClass.oneString);
 
        //相当于以下效果
        ImTheOne1 imTheOne2 = (a) -> new TargetClass("abc");//可以理解为，利用另一个类的签名相同的构造方法，实例化接口方法。
        TargetClass targetClass2 = imTheOne2.getTargetClass("123");
        System.out.println(targetClass2.oneString);
    }
}
//注意：
//
//1，函数式接口的方法getTargetClass(String a)有一个String类型的参数，所以当使用构造方法引用时，引用的是有一个String参数的那个构造，也就是这个：
//
//public TargetClass(String a) {
//    oneString = a;
//}
//2，本例输出的结果是：
//abc
//abc
//
//注意到第二行输出的不是123，而是abc，因为Lambda表达式的原因，当我们执行：
//
//imTheOne2.getTargetClass("123");
//这行代码时，调用的实际上是Lambda体：
//
//new TargetClass("abc");
//所以输出的结果和字符串"123"没关系。
